﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace TreeLayoutPlayground
{
    internal class Tree : Drawable
    {
        protected List<Tree> children;

        public Tree Parent { get; set; }

        /// <summary>
        /// Returns the number of children of this Tree node
        /// </summary>
        public int NumberOfChildren { get { return children.Count; } }

        /// <summary>
        /// Returns an enumerable of this Tree node's children
        /// </summary>
        public IEnumerable<Tree> Children { get { return children.AsEnumerable(); } }

        /// <summary>
        /// Return a specific child of this Tree node
        /// </summary>
        /// <param name="i">Which child to return</param>
        /// <returns>The specified child</returns>
        public Tree this[int i] { get { return children[i]; } }

        /// <summary>
        /// Get the height of the Tree (how many levels are there)
        /// </summary>
        public int TreeHeight
        {
            get
            {
                if (NumberOfChildren == 0) { return 1; }
                return 1 + Children.Aggregate(0,
                    (max, t) =>
                    {
                        return Math.Max(max, t.TreeHeight);
                    });
            }
        }

        /// <summary>
        /// Get how deep in the tree this node is
        /// </summary>
        public int TreeDepth
        {
            get
            {
                int h = 0;
                Tree p = Parent;
                while (p != null)
                {
                    h++;
                    p = p.Parent;
                }

                return h;
            }
        }

        /// <summary>
        /// Get the width of the Tree (how many leaf nodes are there)
        /// </summary>
        public int TreeWidth
        {
            get
            {
                if (NumberOfChildren == 0) { return 1; }
                return Children.Aggregate(0,
                    (sum, t) =>
                    {
                        return sum + t.TreeWidth;
                    });
            }
        }

        /// <summary>
        /// The box representing this node
        /// Coordinates are relative to its own image starting at (0, 0)
        /// </summary>
        public Rectangle MyBox;

        /// <summary>
        /// The translation of this node and its children relative to its parent
        /// </summary>
        public Size MyRelativeTranslation;

        /// <summary>
        /// The size required to display this node and its children
        /// </summary>
        public Size OverallSize;

        /// <summary>
        /// Create a new Tree node with the default size
        /// </summary>
        public Tree()
        {
            children = new List<Tree>();
            MyBox = new Rectangle(0, 0, 200, 50);
            MyRelativeTranslation = Size.Empty;
        }

        /// <summary>
        /// Create a new Tree node with the given width and height
        /// </summary>
        /// <param name="w">The width of this Tree's box</param>
        /// <param name="h">The height of this Tree's box</param>
        public Tree(int w, int h)
        {
            children = new List<Tree>();
            MyBox = new Rectangle(0, 0, w, h);
            MyRelativeTranslation = Size.Empty;
        }

        /// <summary>
        /// Add a child Tree to this Tree
        /// </summary>
        /// <param name="c">The child to add</param>
        public void AddChild(Tree c)
        {
            c.Parent = this;
            children.Add(c);
        }

        /// <summary>
        /// Recursively search this Tree for the given Tree and remove it
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public bool RemoveChild(Tree c)
        {
            if (children.Remove(c)) { return true; }
            return children.Any(ch => { return ch.RemoveChild(c); });
        }

        /// <summary>
        /// Determine which, if any, tree node exists at the given point
        /// </summary>
        /// <param name="click">The coordinates to check</param>
        /// <returns>The selected Tree if found otherwise null</returns>
        public Tree SelectTree(Point click)
        {
            if (MyBox.Contains(click)) { return this; }

            foreach (Tree c in Children)
            {
                Tree tmp = c.SelectTree(Point.Subtract(click, c.MyRelativeTranslation));
                if (tmp != null) { return tmp; }
            }

            return null;
        }

        /// <summary>
        /// Get the point that is the top center position of this Tree's box
        /// </summary>
        /// <param name="t">The amount to offset the result by</param>
        /// <returns>The top center point of this Tree's box</returns>
        public Point GetTopCenter(Size t)
        {
            return new Point(
                t.Width + MyBox.X + (MyBox.Width / 2),
                t.Height + MyBox.Y);
        }

        /// <summary>
        /// Get the point that is the bottom center position of this Tree's box
        /// </summary>
        /// <param name="t">The amount to offset the result by</param>
        /// <returns>The bottom center point of this Tree's box</returns>
        public Point GetBotCenter(Size t)
        {
            return new Point(
                t.Width + MyBox.X + (MyBox.Width / 2),
                t.Height + MyBox.Y + MyBox.Height);
        }

        /// <summary>
        /// This is probably not required anymore
        /// Gets the size required to display this Tree
        /// </summary>
        /// <returns></returns>
        public Size ImageSize() { return OverallSize; }

        /// <summary>
        /// Definitely not require anymore
        /// </summary>
        public void ForceResize() { }

        protected static Pen pen = new Pen(Color.Black);
        protected static Brush brush = new SolidBrush(Color.FromArgb(25, 0, 0, 255));

        /// <summary>
        /// Draw this Tree and its children to the given Graphics
        /// </summary>
        /// <param name="image">The Graphics to draw onto</param>
        public void Draw(Graphics image)
        {
            // Translate to my position relative to my parent
            var ostate = image.Save();
            image.TranslateTransform(MyRelativeTranslation.Width, MyRelativeTranslation.Height);

            // Draw my box
            image.FillRectangle(brush, MyBox);
            image.DrawRectangle(pen, MyBox);

            // Draw each of my children and a line from my bottom center to their top center
            Point bc = GetBotCenter(new Size());
            foreach (Tree c in Children)
            {
                Point tc = c.GetTopCenter(c.MyRelativeTranslation);
                image.DrawLine(pen, bc, tc);
                c.Draw(image);
            }

            // Undo the original translation and return to my parent's coordinate system
            image.Restore(ostate);
        }

        /// <summary>
        /// Save this Tree as an image
        /// </summary>
        /// <param name="file">The file to save the image to</param>
        public void SaveImage(string file)
        {
            Bitmap bitmap = new Bitmap(OverallSize.Width, OverallSize.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            Graphics graphics = Graphics.FromImage(bitmap);

            if (AntiAliasDrawing) { graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; }
            if (AntiAliasText) { graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; }

            graphics.Clear(Color.White);
            Draw(graphics);
            bitmap.Save(file);
        }

        public bool AntiAliasText { get { return true; } }
        public bool AntiAliasDrawing { get { return true; } }
    }
}
